package com.zkh.myutils.template;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import com.zkh.myutils.code.Charsets;
import com.zkh.myutils.expression.Element;
import com.zkh.myutils.expression.Expression;
import com.zkh.myutils.io.file.FileUtils;
import com.zkh.myutils.utils.Regexp;
import com.zkh.myutils.utils.Utils;
import com.zkh.myutils.utils.Regexp.ResultSet;

/**
 * 模板类
 */
public class Template {
	/**
	 * 模板内容
	 */
	private String templateContent;
	/**
	 * 标签列表
	 */
	private List<TemplateElement> elementList;
	/**
	 * 标签层级栈
	 */
	private LinkedList<TemplateElement> tagLvlStackList = new LinkedList<>();
	/**
	 * 构造方法
	 * @param templateFile 模板文件
	 */
	public Template(File templateFile) throws IOException {
		this(templateFile, Charsets.UTF_8);
	}
	/**
	 * 构造方法
	 * @param templateFile 模板文件
	 * @param charset 编码。不指定默认UTF-8，可使用常量Code获取
	 */
	public Template(File templateFile, Charset charset) throws IOException {
		//创建读取流
		try(InputStream inputStream = new FileInputStream(templateFile)){
			read(inputStream, charset);
		}
	}
	
	/**
	 * 构造方法
	 * @param inputStream 模板输入流
	 */
	public Template(InputStream inputStream) throws IOException {
		this(inputStream, Charsets.UTF_8);
	}
	
	/**
	 * 构造方法
	 * @param inputStream 模板输入流
	 * @param charset 编码。不指定默认UTF-8，可使用常量Code获取
	 */
	public Template(InputStream inputStream, Charset charset) throws IOException {
		read(inputStream, charset);
	}
	
	/**
	 * 读取模板内容
	 * @param inputStream 模板流
	 * @param charset 编码
	 */
	private void read(InputStream inputStream, Charset charset){
		//初始化模板元素
		elementList = new ArrayList<>();
		//读取文本
		templateContent = FileUtils.readAll(inputStream, charset);
		//过滤注释
		templateContent = templateContent.replaceAll("<%--[\\s\\S]*?--%>", "");
		//文本转字符
		char[] contentChars = templateContent.toCharArray();
		//长度
		int len = contentChars.length;
		//读取标签
		List<ResultSet> resultSetList = Regexp.getResultSet("(\\$\\{.+?\\})|(<(|/)mt:(foreach|if|elif|else)( |/?>))", templateContent);
		//遍历
		resultSetList.forEach(rs->{
			//获取内容
			String content = rs.getResult();
			//表达式
			if(content.startsWith("$")) {
				//模板元素对象
				TemplateElement element = new TemplateElement();
				//表达式
				element.setElemType(TemplateElement.TYPE_EXP);
				//模板表达式
				TemplateExpression expression = new TemplateExpression(Expression.newInstance(content.substring(2, content.length()-1)));
				//保存表达式
				element.setTemplateExpression(expression);
				//位置
				element.setStart(rs.getStart());
				element.setEnd(rs.getEnd()-1);
				//元素栈为空，则为外层表达式
				if(tagLvlStackList.isEmpty()) {
					//保存元素
					elementList.add(element);
				}//标签内表达式
				else {
					//获取标签元素
					TemplateElement tagElement = tagLvlStackList.getLast();
					//保存
					tagElement.addChild(element);
				}
			}//结束标签
			else if(content.startsWith("</")) {
				//获取内容
				String tagName = content.substring(5, content.length()-1);
				//弹出末尾元素
				TemplateElement last = tagLvlStackList.pollLast();
				//标签对象
				TemplateTag tag = last.getTemplateTag();
				//弹出标签
				if(!tagName.equals(tag.getTagName())) {
					throw new TemplateException("标签解析失败：" + tagName);
				}
				//设置结束位置
				last.setEnd(rs.getEnd() - 1);
				//设置标签位置
				tag.setEndStart(rs.getEnd() - content.length());
				//if标签，需要处理elif和else分支
				if("if".equals(tagName)) {
					//获取子级元素
					List<TemplateElement> children = last.getChildElementList();
					//elif和else分支标签
					List<Integer> branchIndexs = last.initBranchIndexList();
					//标签标识（0初始，1：elif，2：else）
					int tagInd = 0;
					//处理elif和else节点标签
					for(int i=0,size=children.size(); i<size; i++) {
						//当前元素
						TemplateElement e = children.get(i);
						//标签元素
						if(e.getElemType()==TemplateElement.TYPE_TAG) {
							//标签名
							String childTagName = e.getTemplateTag().getTagName();
							//elif
							if("elif".equals(childTagName)) {
								//已经有else
								if(tagInd==2) {
									throw new TemplateException("elif标签不能出现在else标签之后");
								}
								//设置标识
								tagInd = 1;
								//保存
								branchIndexs.add(i);
							}else if("else".equals(childTagName)) {
								//已经有else
								if(tagInd==2) {
									throw new TemplateException("重复的else标签");
								}
								//设置标识
								tagInd = 2;
								//保存
								branchIndexs.add(i);
							}
						}
					}
				}
				//判断栈是否为空
				if(tagLvlStackList.isEmpty()) {
					//保存
					elementList.add(last);
				}else {
					tagLvlStackList.getLast().addChild(last);
				}
			}//开始标签
			else {
				//模板元素对象
				TemplateElement element = new TemplateElement();
				//开始位置
				element.setStart(rs.getStart());
				//索引（冒号后面位置）
				int i = rs.getStart() + 4;
				/** 解析开始 **/
				//标签对象
				TemplateTag tag = new TemplateTag();
				//保存标签对象
				element.setElemType(TemplateElement.TYPE_TAG);
				element.setTemplateTag(tag);
				//临时
				StringBuilder sb = new StringBuilder();
				//结束标识
				boolean end = false;
				//属性key
				String key = null;
				//保存
				sb.append(contentChars[i]);
				//下一字符
				char next = i+1>=len ? '\0' : contentChars[i+1];
				//获取标签名称
				while(next!='\0') {
					//如果是结束
					if(next=='>') {
						//保存位置
						tag.setBeginEnd(i+1);break;
					}else if(next=='/') {
						//下一字符必须是>
						if(i+2>=len || contentChars[i+2]!='>') {
							throw new TemplateException("标签格式错误，结束标签/后面必须紧跟>符号");
						}
						//结束
						end = true;
						//标签名称
						if(tag.getTagName()==null) {
							tag.setTagName(sb.toString());
						}
						//保存位置
						tag.setBeginEnd(i+2);
						//设置结束位置
						element.setEnd(i+2);
						break;
					}//等号
					else if(next=='=') {
						//保存key并重置字符容器
						key = sb.toString();sb.setLength(0);
						//索引加1
						i++;
						//下一个字符
						next = i+1>=len ? '\0' : contentChars[i+1];
						//判断引号
						if(next!='\'' && next!='"') {
							throw new TemplateException("标签格式错误，等号后面不是引号：" + next);
						}
						//获取字符
						i = Element.getString(i+1, len, next, contentChars, sb, true);
						//保存值
						tag.setAttr(key, sb.toString());
						//重置
						key = null;sb.setLength(0);
						//下一个字符
						next = i+1>=len ? '\0' : contentChars[i+1];
					}
					//分隔符，视为标签名
					else if(next==' '){
						//如果有内容
						if(sb.length()>0) {
							tag.setTagName(sb.toString());
							//清除
							sb.setLength(0);
						}
						//索引加1
						i++;
						//下一个字符
						next = i+1>=len ? '\0' : contentChars[i+1];
					}else {
						//保存
						sb.append(next);
						//索引加1
						i++;
						//下一个字符
						next = i+1>=len ? '\0' : contentChars[i+1];
					}
				}
				//如果是结束
				if(end) {
					//判断栈是否为空
					if(tagLvlStackList.isEmpty()) {
						//保存
						elementList.add(element);
					}else {
						tagLvlStackList.getLast().addChild(element);
					}
				}//未结束，压入栈
				else {
					tagLvlStackList.add(element);
				}
			}
		});
	}
	
	/**
	 * 获取模板原始内容
	 * @return
	 */
	public String getTemplateContent() {
		return templateContent;
	}
	/**
	 * 渲染模板，返回渲染后的文本。无参数。
	 * <p><b>解析一定要从后面往前面解析、从内层往外层解析，否则会影响索引位置</b></p>
	 * @return
	 */
	public String render() {
		return render(new HashMap<>());
	}

	/**
	 * 根据所给的数据渲染模板，返回渲染后的文本。
	 * <p><b>解析一定要从后面往前面解析、从内层往外层解析，否则会影响索引位置</b></p>
	 * @param params 参数为键值对，依次为{key1, value1, key2, value2[,..., keyN, valueN]}
	 */
	public String render(Object... params) {
		return render(Utils.getMap(new HashMap<String, Object>(), params));
	}
	
	/**
	 * 根据所给的数据渲染模板，返回渲染后的文本。
	 * <p><b>解析一定要从后面往前面解析、从内层往外层解析，否则会影响索引位置</b></p>
	 * @param dataMap 数据
	 */
	public String render(Map<String, ? extends Object> dataMap) {
		//检查参数
		Objects.requireNonNull(dataMap);
		//备份参数
		Map<String, Object> paramMap = new HashMap<String, Object>(dataMap);
		//最终结果内容
		String content = templateContent;
		//遍历元素
		for(int i=elementList.size()-1; i>=0; i--) {
			//获取元素
			TemplateElement e = elementList.get(i);
			//获取元素解析内容
			String elemContent = e.getContent(e, templateContent, paramMap);
			//替换
			content = content.substring(0, e.getStart()) + elemContent + content.substring(e.getEnd()+1);
		}
		//返回
		return content;
	}
}
